(ns packet.bytes
  (:import java.nio.ByteBuffer
           io.netty.buffer.ByteBuf))

;;----------------------------------------------------------------------
;;不使用自动更新索引的写操作是因为有些类型的槽位构建操作在遇到异常时要选择另外的槽位重试
(defprotocol IndexedBytes
  (getb [this n] "获取第n字节的内容")
  (setb [this n v] "设置第n字节的内容，返回更改后的对象")
  (size [this] "可供读取的字节数"))

(extend-protocol IndexedBytes
  ;;下面对字节数组的扩展必须放在第一位
  (Class/forName "[B")
  (getb [this n] (bit-and (aget this n) 0xff))
  (setb [this n v] (aset-byte this n (unchecked-byte v)) this)
  (size [this] (alength this))

  ;;标准java.nio中的字节缓冲区
  java.nio.ByteBuffer
  (getb [this n] (bit-and (.get this n) 0xff))
  (setb [this n v] (.put this n (unchecked-byte v)))
  (size [this] (.limit this))

  ;;netty中使用的字节缓冲区
  io.netty.buffer.ByteBuf
  (getb [this n] (bit-and (.getByte this n) 0xff))
  (setb [this n v] (.setByte this n (unchecked-byte v)))
  (size [this] (.writerIndex this))

  ;;下面的可索引类型实际上不限于存放字节，任何内容都可以
  ;;如果以向量作为存放目的地，则必须是transient类型的
  clojure.lang.Indexed
  (getb [this n] (nth this n))
  (setb [this n v] (assoc! this n v))
  (size [this] (count this)))

;;不要直接使用getb和setb，使用下面的get-byte和set-byte函数

(def ^:dynamic *getb* getb)
(def ^:dynamic *setb* setb)

(defn get-byte
  "读取一个字节，将取出的字节变换为正整数"
  [data pos]
  #_(bit-and (*getb* data pos) 0xff)
  (*getb* data pos))

(defn get-bytes
  "读取多个字节，将取出的字节变换为正整数，返回一个字节序列"
  [data pos len]
  (map #(get-byte data (+ pos %))
       (range len)))

(defn set-byte
  "在指定位置设置一个字节．如果没有指定位置，则追加一个字节．
  提供的值v可以不在字节范围，存储前会转换到-128到127之间"
  [data pos v]
  (*setb* data pos v))

(defn set-bytes
  "将bs字节序列填补到data的pos位置处，覆盖原来的内容"
  [data pos bs]
  (if-let [b (first bs)]
    (recur (set-byte data pos b)
           (inc pos)
           (rest bs))
    data))

;;----------------------------------------------------------------------

#_(defn t1
  []
  (let [buf (byte-array 50)
        pos (:pos (build m buf))]
    (println "构建的报文如下:")
    (dotimes [i (bs/size buf)]
      (println (bs/get-byte buf i)))
    (println "对以上报文的解析结果为:")
    (parse buf)))

#_(defn t2
  []
  (let [buf (java.nio.ByteBuffer/allocate 50)
        pos (:pos (build m buf))]
    (.limit buf pos)
    (println "构建的报文如下:")
    (dotimes [i (bs/size buf)]
      (println (bs/get-byte buf i)))
    (println "对以上报文的解析结果为:")
    (parse buf)))

#_(defn t3
  []
  (let [buf (io.netty.buffer.Unpooled/buffer 50)
        pos (:pos (build m buf))]
    (.writerIndex buf pos)
    (println "构建的报文如下:")
    (dotimes [i (bs/size buf)]
      (println (bs/get-byte buf i)))
    (println "对以上报文的解析结果为:")
    (parse buf)))

